若应用程序似乎运行缓慢，可能想知道代码中的时间花在了哪里，可以用XRay检测代码可以帮助完成这项任务。每个函数进入和退出时，都会向运行时库插入一个特殊的调用。允许计算函数调用的频率，以及在函数中花费的时间。可以在llvm/lib/XRay/目录中找到检测通道的实现，其运行时是compiler-rt的一部分。

下面的示例源代码中，使用ussleep()函数模拟实际工作。func1()函数休眠10µs，func2()函数调用func1()或休眠100µs，取决于n参数是奇数还是偶数。在main()函数中，两个函数都在循环中调用。需要在xraydemo.c文件中保存以下源码:

\begin{cpp}
#include <unistd.h>

void func1() { usleep(10); }

void func2(int n) {
    if (n % 2) func1();
    else usleep(100);
}

int main(int argc, char *argv[]) {
    for (int i = 0; i < 100; i++) { func1(); func2(i); }
    return 0;
}
\end{cpp}

要在编译期间启用XRay检测，需要指定-fxrayinstrument选项，指令少于200条的函数不会检测。这是因为这是开发人员定义的任意阈值。我们的例子中，函数不会检测。该阈值可以通过-fxrayinstruction-threshold=选项指定。

或者，可以添加一个function属性来控制是否应该检测一个函数，添加以下原型将总是检测函数:

\begin{cpp}
void func1() __attribute__((xray_always_instrument));
\end{cpp}

通过使用xray\_never\_instrument属性，可以关闭函数的检测功能。

现在将使用命令行选项并编译xraydemo.c文件:

\begin{shell}
$ clang -fxray-instrument -fxray-instruction-threshold=1 -g xraydemo.c -o xraydemo
\end{shell}

生成的二进制文件中，检测在默认情况下关闭。若运行二进制文件，将注意到与未检测的二进制文件相比没有区别。XRAY\_OPTIONS环境变量用于控制运行时数据的记录，要启用数据收集，可以运行如下应用程序:

\begin{shell}
$ XRAY_OPTIONS="patch_premain=true xray_mode=xray-basic" ./xraydemo
\end{shell}

xray\_mode=xray-basic选项告诉运行时我们想要使用基本模式。这种模式下，将收集所有运行时数据，这可能导致生成较大的日志文件。当给出patch\_premain=true选项时，还会检测在main()函数之前运行的函数。

执行该命令后，采集数据所在目录下将创建一个新文件。需要使用llvm-xray工具从该文件中提取可读信息。

llvm-xray工具支持各种子命令，可以使用account子命令提取一些基本统计信息。例如，要获得调用次数最多的前10个函数，可以添加-top=10选项来限制输出，并添加-sort=count选项来指定函数调用计数作为排序标准，还可以使用-sortorder=选项影响排序顺序。

可以运行以下命令来从程序中获取统计信息:

\begin{shell}
$ llvm-xray account xray-log.xraydemo.xVsWiE --sort=count\
--sortorder=dsc --instr_map ./xraydemo
Functions with latencies: 3
funcid   count      sum function
     1     150 0.166002 demo.c:4:0: func1
     2     100 0.543103 demo.c:9:0: func2
     3       1 0.655643 demo.c:17:0: main
\end{shell}

func1()函数调用的次数最多，还可以看到在此函数中花费的累积时间。这个例子只有三个函数，所以-top=选项在这里没有明显的效果，但是对于实际的应用程序，是非常有用的。

从收集的数据中，可以重建运行时期间发生的所有堆栈帧，使用stack子命令查看top 10的堆叠信息。为了简洁起见，这里简化了显示的输出:

\begin{shell}
$ llvm-xray stack xray-log.xraydemo.xVsWiE –instr_map ./xraydemo
Unique Stacks: 3
Top 10 Stacks by leaf sum:

Sum: 1325516912
lvl   function   count            sum
#0    main           1     1777862705
#1    func2         50     1325516912

Top 10 Stacks by leaf count:

Count: 100
lvl   function   count             sum
#0    main           1      1777862705
#1    func1        100       303596276
\end{shell}

堆栈帧是函数如何调用的序列。func2()函数由main()函数调用，这是累积时间最长的堆栈帧。深度取决于调用了多少函数，堆栈帧通常很大。

此子命令还可以用于从堆栈帧创建火焰图，可以轻松地确定哪些函数具有较大的累积运行时。输出是带有计数和运行时信息的堆栈帧。使用flamegraph.pl脚本，可以将数据转换为可伸缩的矢量图形(SVG)文件，并可以在浏览器中查看该文件。

使用以下命令，指示llvm-xray使用-all-stacks选项输出所有堆栈帧。使用-stack-format =flame选项，输出将是flamegraph.pl脚本所期望的格式。使用-aggregate-type选项，可以选择是按总时间统计堆栈帧，还是按调用次数统计堆栈帧。llvm-xray的输出通过管道输入到flamegraph.pl脚本中，结果输出保存在flame.svg文件中:

\begin{shell}
$ llvm-xray stack xray-log.xraydemo.xVsWiE --all-stacks\
    --stack-format=flame --aggregation-type=time\
    --instr_map ./xraydemo | flamegraph.pl >flame.svg
\end{shell}

运行命令并生成新的火焰图后，可以在浏览器中打开生成的flame.svg文件。图表如下:

\myGraphic{1.0}{content/part3/chapter10/images/1.png}{图10.1 - llvm-xray生成的火焰图}

火焰图乍一看可能会令人困惑，因为x轴没有通常的时间流逝含义。相反，函数只是按名称的字母顺序排序。此外，火焰图的y轴显示堆栈深度，底部从零开始计数。颜色的选择要有很好的对比，没有别的意思。从前面的图中，可以很容易地确定调用层次结构和在函数中花费的时间。

只有将鼠标移动到表示堆叠帧的矩形上时，才会显示堆叠帧的相关信息。通过单击该框架，可以放大该堆栈框架。若想确定值得优化的函数，火焰图会有很大的帮助。要了解更多关于火焰图的知识，请访问火焰图的发明者Brendan Gregg的主页:\url{http://www.brendangregg.com/flamegraphs.html}。

此外，可以使用转换子命令将数据转换为.yaml格式，或Chrome跟踪查看器可视化使用的格式。后者是另一种从数据创建图形的好方法。将数据保存在xray.evt文件后，执行如下命令:

\begin{shell}
$ llvm-xray convert --output-format=trace_event\
    --output=xray.evt --symbolize --sort\
    --instr_map=./xraydemo xray-log.xraydemo.xVsWiE
\end{shell}

若不指定-symbol选项，则结果图中不会显示函数名。

完成后，打开Chrome并输入chrome:///tracing。接下来，单击Load按钮来加载xray.evt文件。将看到以下可视化数据:

\myGraphic{1.0}{content/part3/chapter10/images/2.png}{图10.2 - 由llvm-xray生成的可视化Chrome跟踪查看器}

在此视图中，堆栈帧按函数调用发生的时间排序。要进一步了解可视化，请阅读\url{https://www.chromium.org/developers/how-tos/trace-event-profiling-tool}上的教程。

\begin{myTip}{Tip}
llvm-xray工具具有更多适用于性能分析的功能。可以在LLVM网站\url{https://llvm.org/docs/XRay.html}和\url{https://llvm.org/docs/XRayExample.html}上阅读相关内容。
\end{myTip}

本节中，了解如何使用XRay检测应用程序，如何收集运行时信息，以及如何可视化该数据。可以利用这些知识，找到应用程序中的性能瓶颈。

识别应用程序中错误的另一种方法是分析源代码，可以通过clang静态分析器完成。























